Skip to content

Improvements for "StringValuesBuilder#appendAll" #4870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

otobrglez
Copy link

@otobrglez otobrglez commented May 20, 2025

Hey!

While using Ktor, I found that it could be more ergonomic if StringValuesBuilder also supported mutations with maps. This is my humble improvement that could make this experience better.

Subsystem
ktor-utils

Motivation
I wanted to build URLs with something like this:

val builder = URLBuilder("https://github.com").apply {
    parameters.appendAll(mapOf("foo" to "bar", "baz" to "qux"))
    parameters.appendAllIterable(mapOf("test" to listOf("1", "2", "3")))
}

println(builder.buildString())
// => https://github.com?foo=bar&baz=qux&test=1&test=2&test=3

Solution

  • My PR adds two methods named appendAll to StringValuesBuilder inside StringValues.kt
  • One method is for Map<String, String> and another for Map<String, Iterable<String>>
  • Classes that implement this interface get these abilities for free as the existing mechanisms are reused (such as append and appendAll)

P.S.: Please be gentle, as this is my first PR to Ktor.

Regards

- Oto

Copy link

coderabbitai bot commented May 20, 2025

Walkthrough

The StringValuesBuilder interface was extended with four new appendAll extension functions that accept various collections of key-value pairs, including varargs of pairs and maps with string or iterable values. The appendMissing method declaration was reordered. A corresponding test was added to verify these extensions. Additionally, new default implementation classes with static appendAll methods were added for ParametersBuilder and StringValuesBuilder in their respective API modules.

Changes

File(s) Change Summary
ktor-utils/common/src/io/ktor/util/StringValues.kt Added import for JvmName, reordered appendMissing method, and introduced four appendAll extension functions with different parameter types.
ktor-utils/common/test/io/ktor/util/StringValuesTest.kt Added appendAllExtensions test method to validate the new appendAll extension functions on StringValues.Builder.
ktor-http/api/ktor-http.api Added ParametersBuilder$DefaultImpls class with static appendAll and appendAllIterable methods.
ktor-utils/api/ktor-utils.api Added StringValuesBuilder$DefaultImpls class with static appendAll and appendAllIterable methods; added four static appendAll overloads in StringValuesKt.
ktor-utils/api/ktor-utils.klib.api Added four new overloaded appendAll extension functions to StringValuesBuilder for various map and vararg pair inputs.
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@bjhham bjhham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, could you please run ./gradlew apiDump and commit the result to update the API snapshot?

Copy link
Member

@osipxd osipxd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @otobrglez, do you want to continue work on this PR?
We're preparing release 3.2.0 this week so it is the best time to include new APIs.

@otobrglez
Copy link
Author

Hey @otobrglez, do you want to continue work on this PR? We're preparing release 3.2.0 this week so it is the best time to include new APIs.

Yeah, I would love to. Please guide me. 😅

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
ktor-utils/common/test/io/ktor/util/StringValuesTest.kt (1)

120-135: Enhance test assertions for comprehensive validation.

The test covers all four new appendAll extension functions, which is excellent. However, the assertions could be more thorough to ensure all values are correctly stored:

Consider enhancing the assertions to verify all values:

     assertTrue { map.contains("foo") }
     assertTrue { map.contains("foo", "bar") }
     assertTrue { map.contains("baz") }
+    assertTrue { map.contains("baz", "bar") }
+    assertTrue { map.contains("baz", "bor") }
+    assertEquals(listOf("bar", "bor"), map.getAll("baz"))
     assertTrue { map.contains("rar") }
+    assertTrue { map.contains("rar", "boo") }
+    assertTrue { map.contains("rar", "too") }
+    assertEquals(listOf("boo", "too"), map.getAll("rar"))
     assertTrue { map.contains("goo", "boo") }
     assertTrue { map.contains("doo", "dah") }

This follows the pattern established by other tests in the file (like twoValueMap and threeValueCaseInsensitiveMap) that use both contains() and getAll() to verify multi-value scenarios.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef228c5 and 7c99cc8.

📒 Files selected for processing (2)
  • ktor-utils/common/src/io/ktor/util/StringValues.kt (3 hunks)
  • ktor-utils/common/test/io/ktor/util/StringValuesTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ktor-utils/common/src/io/ktor/util/StringValues.kt
🧰 Additional context used
🧬 Code Graph Analysis (1)
ktor-utils/common/test/io/ktor/util/StringValuesTest.kt (1)
ktor-utils/common/src/io/ktor/util/StringValues.kt (9)
  • appendAll (117-117)
  • appendAll (118-118)
  • appendAll (258-262)
  • appendAll (270-275)
  • appendAll (441-445)
  • appendAll (475-477)
  • appendAll (487-490)
  • appendAll (500-503)
  • appendAll (513-516)

@otobrglez
Copy link
Author

Hey!

  • @osipxd I've implemented it the way you've proposed
  • @bjhham I've run the apiDump task (my MacBook almost ran out of the room on its spinning fans)

I hope this will do well.

Regards,

- Oto

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c99cc8 and 8be2fa7.

📒 Files selected for processing (4)
  • ktor-http/api/ktor-http.api (1 hunks)
  • ktor-utils/api/ktor-utils.api (2 hunks)
  • ktor-utils/api/ktor-utils.klib.api (1 hunks)
  • ktor-utils/common/test/io/ktor/util/StringValuesTest.kt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ktor-utils/common/test/io/ktor/util/StringValuesTest.kt
🔇 Additional comments (3)
ktor-utils/api/ktor-utils.api (1)

395-398: LGTM! Core functionality aligns with PR objectives.

The addition of default implementations for appendAll methods with Map parameters supports the stated goal of enabling bulk parameter appending from maps.

ktor-utils/api/ktor-utils.klib.api (1)

943-946: LGTM! Excellent ergonomic improvements to StringValuesBuilder.

These four new appendAll extension functions provide comprehensive support for bulk appending operations from various collection types. The implementation correctly:

  • Supports both Map<String, String> and Map<String, Iterable<String>> for different use cases
  • Provides vararg overloads for Pair<String, String> and Pair<String, Iterable<String>>
  • Returns StringValuesBuilder for method chaining
  • Follows consistent naming conventions with existing appendAll methods

This enhancement will significantly improve the developer experience when building URL parameters from maps, exactly as described in the PR objectives.

ktor-http/api/ktor-http.api (1)

831-834:

✅ Verification successful

Verify method signatures for appendAll overloads.

Both appendAll and appendAllIterable methods have identical JVM signatures (Lio/ktor/http/ParametersBuilder;Ljava/util/Map;)V, which raises concerns about method resolution and API design.

According to the PR objectives, the intent is to support:

  • Map<String, String>
  • Map<String, Iterable<String>>

However, due to type erasure, both would have the same erased signature. Please verify:

  1. Method Differentiation: How are these methods distinguished at runtime when both accept Map with the same erased signature?

  2. Naming Convention: The appendAllIterable name seems verbose compared to standard Kotlin overloading patterns. Is this compiler-generated name mangling, or was this intentionally chosen?

  3. API Consistency: Ensure this aligns with the corresponding StringValuesBuilder$DefaultImpls mentioned in the AI summary.

Let me verify the actual source code to understand how these methods are differentiated:


🏁 Script executed:

#!/bin/bash
# Search for ParametersBuilder appendAll implementations to understand the actual method signatures
ast-grep --pattern 'fun appendAll($$$) {
  $$$
}'

# Also search for any StringValuesBuilder DefaultImpls for comparison
rg -A 5 "DefaultImpls" 

# Look for the actual extension function declarations
ast-grep --pattern 'fun ParametersBuilder.appendAll($$$) {
  $$$
}'

Length of output: 28971


DefaultImpls methods are correctly differentiated

  • The Kotlin compiler emits two distinct static methods—appendAll and appendAllIterable—to prevent JVM signature clashes after type erasure.
  • This naming pattern exactly mirrors the implementation in StringValuesBuilder$DefaultImpls, ensuring consistency across the API.

No changes required.

Copy link
Member

@osipxd osipxd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the quick fix! Well done! I have only a few minor comments.
To keep your laptop in the room, try ./gradlew :ktor-utils:apiDump 🙂

* @param values the map containing key-value pairs to append
* @return this builder instance
*/
@JvmName("appendAllIterableWithMapList")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this annotation could be dropped

* @param values the map containing key-value pairs to append where values are [Iterable] of strings
* @return this builder instance
*/
@JvmName("appendAllIterableWithMap")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅 The name can be the same as for vararg function - appendAllIterable as argument types are different and signatures won't clash

Comment on lines +831 to +834
public final class io/ktor/http/ParametersBuilder$DefaultImpls {
public static fun appendAll (Lio/ktor/http/ParametersBuilder;Ljava/util/Map;)V
public static fun appendAllIterable (Lio/ktor/http/ParametersBuilder;Ljava/util/Map;)V
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... BCV has included some extra lines from previous changes. Seems like a bug. Could you remove these lines and some extra lines in ktor-utils.api?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants